/*
 *  NFCSigning - Open source library for signing/validation of NDEF messages
 *  Copyright (C) 2009-2010 The NFCSigning Team
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2.1 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 *
 */
/*
 * AlgorithmTestCase.java
 * JMUnit based test
 *
 * Created on 2008-sep-26, 16:07:47
 */

package org.nfcsigning.algorithm;

import org.nfcsigning.MessageToSign;
import org.nfcsigning.SignedMessage;
import org.nfcsigning.certificate.X509Certificate;
import org.nfcsigning.record.CertificateBytes;
import org.nfcsigning.record.CertificateField;
import org.nfcsigning.record.SignatureRecord;
import org.nfcsigning.record.URIRecord;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import jmunit.framework.cldc11.AssertionFailedException;
import jmunit.framework.cldc11.TestCase;
import org.bouncycastle.crypto.CryptoException;
import org.bouncycastle.crypto.DataLengthException;

//#ifdef MyJSR257
//# import my.javax.microedition.contactless.ndef.NDEFMessage;
//# import my.javax.microedition.contactless.ndef.NDEFRecord;
//#else
import javax.microedition.contactless.ndef.NDEFMessage;
import javax.microedition.contactless.ndef.NDEFRecord;
//#endif

/**
 * @author emarkki
 */
public class AlgorithmTestCase extends TestCase {
    
    public AlgorithmTestCase() {
        // The first parameter of inherited constructor is the number of test cases
        super(7, "AlgorithmTestCase");
    }            

    public void test(int testNumber) throws Throwable {
        
        System.out.println("\n\n------ T E S T  " + testNumber + "  -------");
        
        switch (testNumber) {
            case 0:
                test1();
                break;
            case 1:
                testSignAndVerify();
                break;
            case 2:
                testVerifyRSASSAPSS();
                break;
            case 3:
                testSignWithPKCS1();
                break;
            case 4:
                testSignWithECDSA();
                break;
            case 5:
                testSignWithDSA();
                break;
            case 6:
                testEncodeDecodeAsInX962();
                break;
            case 7:

                break;
            case 8:
                
                break;
            case 9:

                break;
            case 10:

                break;
            case 11:

                break;
            case 12:

                break;
            case 13:

                break;
            case 14:

                break;
            case 15:

                break;
            case 16:

                break;
            case 17:

                break;
            case 18:

                break;
            case 19:

                break;
            case 20:

                break;
            case 21:

                break;
            case 22:

                break;
            case 23:

                break;
            case 24:
                
                break;
            case 25:
                
                break;
            default:
                break;
        }
    }

    

    
    /**
     * Dummy test
     */
    public void test1() throws AssertionFailedException {
        System.out.println("test1");
        

    }
    
    private void testSignAndVerify() throws AssertionFailedException {
        System.out.println("testSignAndVerify");
            
        testSignAndVerifyPSS("msg1", "MESSAGE TO SIGN", Files.MARKUS1_PRIV, Files.MARKUS1);
        testSignAndVerifyPSS("msg2", "Another message to sign", Files.MARKUS4_PRIV, Files.MARKUS4);
        testSignAndVerifyPSS("msg3", "", Files.MARKUS1_PRIV, Files.MARKUS1);
        testSignAndVerifyPSS("msg4", "Message...", Files.KALLE_KARLSSON_PRIV, Files.KALLE_KARLSSON);
                
        testSignAndVerifyPKCS1("msg1 PKCS1", "MESSAGE TO SIGN", Files.MARKUS1_PRIV, Files.MARKUS1);
        testSignAndVerifyPKCS1("msg2 PKCS1", "Another message to sign", Files.MARKUS4_PRIV, Files.MARKUS4);
        testSignAndVerifyPKCS1("msg3 PKCS1", "", Files.MARKUS1_PRIV, Files.MARKUS1);
        testSignAndVerifyPKCS1("msg4 PKCS1", "Message...", Files.KALLE_KARLSSON_PRIV, Files.KALLE_KARLSSON);
        
        testSignAndVerifyECDSA("msg5", "Message123...", Files.MORA_NISSE_PRIVATE, Files.MORA_NISSE);

        testSignAndVerifyDSA("msg6", "A Message...", Files.LARS_NILS_PRIVATE, Files.LARS_NILS);
    }

    public void testSignAndVerifyPSS(String title, String message, String privPath, String pubPath) throws AssertionFailedException {
        try {
            System.out.println(" " + title);

            byte[] coveredBytes = message.getBytes();
            InputStream privIn = getClass().getResourceAsStream(privPath);
            if(privIn == null) {
                fail(title+": No such priv path: " + privPath);
            }
            PrivateKey privKey = new PrivateKey(privIn);

            byte[] signature = RSASSAPSSSigner.signUsingRSASSAPSS(coveredBytes, privKey);
            Utils.dumpAsJavaByteArray(signature);
            
            InputStream certIn = getClass().getResourceAsStream(pubPath);
            if (certIn == null) {
                fail(title + ": No such pub path: " + pubPath);
            }
            PublicKey pubKey = new PublicKey(new X509Certificate(certIn).getSubjectPublicKeyInfo());
            
            assertTrue(title+": consistent", RSASSAPSSVerifier.getInstance().verifySignature(signature, coveredBytes, pubKey));
            
        } catch (DataLengthException ex) {
            ex.printStackTrace();
            fail(title+": DataLength: "+ex.getMessage());
        } catch (CryptoException ex) {
            ex.printStackTrace();
            fail(title+": Crypto: "+ex.getMessage());
        } catch (IOException ex) {
            ex.printStackTrace();
            fail(title+": IOException: "+ex.getMessage());
        }
    }
    
    private void testSignAndVerifyPKCS1(String title, String message, String privPath, String pubPath) throws AssertionFailedException {
        try {
            System.out.println(" " + title);

            byte[] coveredBytes = message.getBytes();
            InputStream privIn = getClass().getResourceAsStream(privPath);
            if(privIn == null) {
                fail(title+": No such priv path: " + privPath);
            }
            PrivateKey privKey = new PrivateKey(privIn);

            byte[] signature = RSASSAPKCS1v15Signer.signUsingRSAPKCS1(coveredBytes, privKey);
//            Utils.dumpAsJavaByteArray(signature);
            
            InputStream certIn = getClass().getResourceAsStream(pubPath);
            if (certIn == null) {
                fail(title + ": No such pub path: " + pubPath);
            }
            PublicKey pubKey = new PublicKey(new X509Certificate(certIn).getSubjectPublicKeyInfo());
            
            assertTrue(title+": consistent", RsaSsaPkcs1v15BcVerifier.getInstance().verifySignature(signature, coveredBytes, pubKey));

            if(ImplementationDetector.getInstance().isSatsaAvailable()) {
                assertTrue(title+": consistent", RsaSsaPkcs1V15SatsaVerifier.getInstance().verifySignature(signature, coveredBytes, pubKey));
            } else {
                System.out.println("Skipping testSignAndVerifyPKCS1 for SATSA");
            }
            
        } catch (SignatureException ex) {
            ex.printStackTrace();
            fail(title+": SignatureException: "+ex.getMessage());
        } catch (DataLengthException ex) {
            ex.printStackTrace();
            fail(title+": DataLength: "+ex.getMessage());
        } catch (CryptoException ex) {
            ex.printStackTrace();
            fail(title+": Crypto: "+ex.getMessage());
        } catch (IOException ex) {
            ex.printStackTrace();
            fail(title+": IOException: "+ex.getMessage());
        }
    }
    
    private void testSignAndVerifyECDSA(String title, String message, String privPath, String pubPath) throws AssertionFailedException {
        try {
            System.out.println(" " + title);

            byte[] coveredBytes = message.getBytes();
            InputStream privIn = getClass().getResourceAsStream(privPath);
            if(privIn == null) {
                fail(title+": No such priv path: " + privPath);
            }
            PrivateKey privKey = new PrivateKey(privIn);

            BigInteger[] rs = ECDSASigner.signUsingECDSA(coveredBytes, privKey);
            
            byte[] signature = ECDSASigner.encodeAsInX962(rs[0], rs[1]);
//            Utils.dumpAsJavaByteArray(signature);
            
            InputStream certIn = getClass().getResourceAsStream(pubPath);
            if (certIn == null) {
                fail(title + ": No such pub path: " + pubPath);
            }
            PublicKey pubKey = new PublicKey(new X509Certificate(certIn).getSubjectPublicKeyInfo());
            
            assertTrue(title+": consistent", ECDSAVerifier.getInstance().verifySignature(signature, coveredBytes, pubKey));

        } catch (SignatureException ex) {
            ex.printStackTrace();
            fail(title+": SignatureException: "+ex.getMessage());
        } catch (DataLengthException ex) {
            ex.printStackTrace();
            fail(title+": DataLength: "+ex.getMessage());
        } catch (CryptoException ex) {
            ex.printStackTrace();
            fail(title+": Crypto: "+ex.getMessage());
        } catch (IOException ex) {
            ex.printStackTrace();
            fail(title+": IOException: "+ex.getMessage());
        }
    }
    
    private void testSignAndVerifyDSA(String title, String message, String privPath, String pubPath) throws AssertionFailedException {
        try {
            System.out.println(" " + title);

            byte[] coveredBytes = message.getBytes();
            InputStream privIn = getClass().getResourceAsStream(privPath);
            if(privIn == null) {
                fail(title+": No such priv path: " + privPath);
            }
            PrivateKey privKey = new PrivateKey(privIn);

            BigInteger[] rs = DSASigner.signUsingDSA(coveredBytes, privKey);
            
            byte[] signature = DSAVerifier.encode(rs[0], rs[1]);
//            Utils.dumpAsJavaByteArray(signature);
            
            InputStream certIn = getClass().getResourceAsStream(pubPath);
            if (certIn == null) {
                fail(title + ": No such pub path: " + pubPath);
            }
            PublicKey pubKey = new PublicKey(new X509Certificate(certIn).getSubjectPublicKeyInfo());
            
            assertTrue(title+": consistent", DSAVerifier.getInstance().verifySignature(signature, coveredBytes, pubKey));

        } catch (DataLengthException ex) {
            ex.printStackTrace();
            fail(title+": DataLength: "+ex.getMessage());
        } catch (CryptoException ex) {
            ex.printStackTrace();
            fail(title+": Crypto: "+ex.getMessage());
        } catch (IOException ex) {
            ex.printStackTrace();
            fail(title+": IOException: "+ex.getMessage());
        }
    }
    
    private void testVerifyRSASSAPSS() throws AssertionFailedException {
        System.out.println("testVerifyRSASSAPSS");
        
         testVerifyRSASSAPSS("msg1", true, "MESSAGE TO SIGN", TestData.pssSignedMsg1, Files.MARKUS1);
         testVerifyRSASSAPSS("msg1 wrong key", false, "MESSAGE TO SIGN", TestData.pssSignedMsg1, Files.MARKUS4);
         testVerifyRSASSAPSS("msg1 wrong msg", false, "_MESSAGE TO SIGN", TestData.pssSignedMsg1, Files.MARKUS4);
         testVerifyRSASSAPSS("msg2", true, "Message...", TestData.pssSignedMsg2, Files.KALLE_KARLSSON);
         
         // TODO Add test for other signatures here...
    }
    
    private void testVerifyRSASSAPSS(String title, boolean consistent, String message, byte[] signature, String certPath) throws AssertionFailedException {
        try {
            System.out.println(" " + title);

            InputStream certIn = getClass().getResourceAsStream(certPath);
            if (certIn == null) {
                fail(title + ": No such path: " + certPath);
            }
            PublicKey cert = new PublicKey(new X509Certificate(certIn).getSubjectPublicKeyInfo());

            byte[] coveredBytes = message.getBytes("utf-8");

            if (consistent) {
                assertTrue(title+": consistent", RSASSAPSSVerifier.getInstance().verifySignature(signature, coveredBytes, cert));
            } else {
                assertFalse(title+": inconsistent", RSASSAPSSVerifier.getInstance().verifySignature(signature, coveredBytes, cert));
            }
        } catch (IOException ex) {
            ex.printStackTrace();
            fail(title + ": IOException: " + ex.getMessage());
        }
    }
    
    private void testSignWithPKCS1() throws AssertionFailedException {
        try {
            System.out.println("testSignWithPKCS1");

            URIRecord uri1 = new URIRecord(new byte[]{(byte) 1}, "http://www.ericsson.com/1");
            URIRecord uri2 = new URIRecord(new byte[]{(byte) 2}, "http://www.ericsson.com/2");

            NDEFRecord[] records = new NDEFRecord[]{uri1.toNDEFRecord(), uri2.toNDEFRecord()};
            NDEFMessage payload = new NDEFMessage(records);
            PrivateKey privKey = new PrivateKey(getClass().getResourceAsStream(Files.KALLE_KARLSSON_PRIV));
            CertificateBytes[] chain = new CertificateBytes[]{new CertificateBytes(new X509Certificate(getClass().getResourceAsStream(Files.KALLE_KARLSSON)).getBytes()), new CertificateBytes(new X509Certificate(getClass().getResourceAsStream(Files.TRAINCOMPANYCA)).getBytes()), new CertificateBytes(new X509Certificate(getClass().getResourceAsStream(Files.STOCKHOLMSLANCA)).getBytes())};

            Signer signer = new RSASSAPKCS1v15Signer(privKey, CertificateField.X509, chain, null);
            
            MessageToSign toSign = new MessageToSign(payload);
            toSign.setSigner(1, signer);
            SignedMessage signed = toSign.signMessage();
            NDEFMessage signedPayload = signed.toNDEFMessage();
            
            System.out.println("records: "+signedPayload.getNumberOfRecords());
            
            try {
                System.out.println("rec0: " + signedPayload.getRecord(0).getRecordType().getName());
                System.out.println("rec1: " + signedPayload.getRecord(1).getRecordType().getName());
                System.out.println("rec2: " + signedPayload.getRecord(2).getRecordType().getName());
                System.out.println("rec3: " + signedPayload.getRecord(3).getRecordType().getName());
                
                assertEquals("rec0=U", URIRecord.RECORD_TYPE, signedPayload.getRecord(0).getRecordType());
                assertEquals("rec1=Sig", SignatureRecord.RECORD_TYPE, signedPayload.getRecord(1).getRecordType());
                assertTrue("rec1=marker", new SignatureRecord(signedPayload.getRecord(1)).isMarker());
                assertEquals("rec2=U", URIRecord.RECORD_TYPE, signedPayload.getRecord(2).getRecordType());
                assertEquals("rec3=Sig", SignatureRecord.RECORD_TYPE, signedPayload.getRecord(3).getRecordType());
                assertFalse("rec3=not marker", new SignatureRecord(signedPayload.getRecord(3)).isMarker());
                
            } catch (Throwable ex) {
                ex.printStackTrace();
                fail("SigRec-fail:" + ex);
            }
            
        } catch (SignatureException ex) {
            ex.printStackTrace();
            fail("SignException: " + ex.getMessage());
        } catch (DataLengthException ex) {
            ex.printStackTrace();
            fail(ex.getMessage());
        } catch (IOException ex) {
            ex.printStackTrace();
            fail(ex.getMessage());
        }
    }
    
    private void testSignWithECDSA() throws AssertionFailedException {
        try {
            System.out.println("testSignWithECDSA");

            URIRecord uri1 = new URIRecord(new byte[]{(byte) 1}, "http://www.ericsson.com/1");
            URIRecord uri2 = new URIRecord(new byte[]{(byte) 2}, "http://www.ericsson.com/2");

            NDEFRecord[] records = new NDEFRecord[]{uri1.toNDEFRecord(), uri2.toNDEFRecord()};
            NDEFMessage payload = new NDEFMessage(records);
            PrivateKey privKey = new PrivateKey(getClass().getResourceAsStream(Files.MORA_NISSE_PRIVATE));
            CertificateBytes[] chain = new CertificateBytes[]{
                new CertificateBytes(new X509Certificate(getClass().getResourceAsStream(Files.MORA_NISSE)).getBytes()), 
                new CertificateBytes(new X509Certificate(getClass().getResourceAsStream(Files.DALARNA_CA)).getBytes()) 
            };

            Signer signer = new ECDSASigner(privKey, CertificateField.X509, chain, null);
            MessageToSign toSign = new MessageToSign(payload);
            toSign.setSigner(new int[]{1}, signer);
            SignedMessage signed = toSign.signMessage();
            NDEFMessage signedPayload = signed.toNDEFMessage();
            
            System.out.println("records: "+signedPayload.getNumberOfRecords());
            
            try {
                System.out.println("rec0: " + signedPayload.getRecord(0).getRecordType().getName());
                System.out.println("rec1: " + signedPayload.getRecord(1).getRecordType().getName());
                System.out.println("rec2: " + signedPayload.getRecord(2).getRecordType().getName());
                System.out.println("rec3: " + signedPayload.getRecord(3).getRecordType().getName());
                
                assertEquals("rec0=U", URIRecord.RECORD_TYPE, signedPayload.getRecord(0).getRecordType());
                assertEquals("rec1=Sig", SignatureRecord.RECORD_TYPE, signedPayload.getRecord(1).getRecordType());
                assertTrue("rec1=marker", new SignatureRecord(signedPayload.getRecord(1)).isMarker());
                assertEquals("rec2=U", URIRecord.RECORD_TYPE, signedPayload.getRecord(2).getRecordType());
                assertEquals("rec3=Sig", SignatureRecord.RECORD_TYPE, signedPayload.getRecord(3).getRecordType());
                assertFalse("rec3=not marker", new SignatureRecord(signedPayload.getRecord(3)).isMarker());
                
            } catch (Throwable ex) {
                ex.printStackTrace();
                fail("SigRec-fail:" + ex);
            }
            
        } catch (SignatureException ex) {
            ex.printStackTrace();
            fail("SignException: " + ex.getMessage());
        } catch (DataLengthException ex) {
            ex.printStackTrace();
            fail(ex.getMessage());
        } catch (IOException ex) {
            ex.printStackTrace();
            fail(ex.getMessage());
        }
    }
    
    private void testSignWithDSA() throws AssertionFailedException {
        try {
            System.out.println("testSignWithDSA");

            URIRecord uri1 = new URIRecord(new byte[]{(byte) 1}, "http://www.ericsson.com/1");
            URIRecord uri2 = new URIRecord(new byte[]{(byte) 2}, "http://www.ericsson.com/2");

            NDEFRecord[] records = new NDEFRecord[]{uri1.toNDEFRecord(), uri2.toNDEFRecord()};
            NDEFMessage payload = new NDEFMessage(records);
            PrivateKey privKey = new PrivateKey(getClass().getResourceAsStream(Files.LARS_NILS_PRIVATE));
            CertificateBytes[] chain = new CertificateBytes[]{
                new CertificateBytes(new X509Certificate(getClass().getResourceAsStream(Files.LARS_NILS)).getBytes()), 
                new CertificateBytes(new X509Certificate(getClass().getResourceAsStream(Files.DALATRAFIK_CA)).getBytes()), 
                new CertificateBytes(new X509Certificate(getClass().getResourceAsStream(Files.DALARNA_CA)).getBytes()),
            };

            Signer signer = new DSASigner(privKey, CertificateField.X509, chain, null);
            MessageToSign toSign = new MessageToSign(payload);
            toSign.setSigner(new int[]{1}, signer);
            SignedMessage signed = toSign.signMessage();
            NDEFMessage signedPayload = signed.toNDEFMessage();
            
            System.out.println("records: "+signedPayload.getNumberOfRecords());
            
            try {
                System.out.println("rec0: " + signedPayload.getRecord(0).getRecordType().getName());
                System.out.println("rec1: " + signedPayload.getRecord(1).getRecordType().getName());
                System.out.println("rec2: " + signedPayload.getRecord(2).getRecordType().getName());
                System.out.println("rec3: " + signedPayload.getRecord(3).getRecordType().getName());
                
                assertEquals("rec0=U", URIRecord.RECORD_TYPE, signedPayload.getRecord(0).getRecordType());
                assertEquals("rec1=Sig", SignatureRecord.RECORD_TYPE, signedPayload.getRecord(1).getRecordType());
                assertTrue("rec1=marker", new SignatureRecord(signedPayload.getRecord(1)).isMarker());
                assertEquals("rec2=U", URIRecord.RECORD_TYPE, signedPayload.getRecord(2).getRecordType());
                assertEquals("rec3=Sig", SignatureRecord.RECORD_TYPE, signedPayload.getRecord(3).getRecordType());
                assertFalse("rec3=not marker", new SignatureRecord(signedPayload.getRecord(3)).isMarker());
                
            } catch (Throwable ex) {
                ex.printStackTrace();
                fail("SigRec-fail:" + ex);
            }
            
        } catch (SignatureException ex) {
            ex.printStackTrace();
            fail("SignException: " + ex.getMessage());
        } catch (DataLengthException ex) {
            ex.printStackTrace();
            fail(ex.getMessage());
        } catch (IOException ex) {
            ex.printStackTrace();
            fail(ex.getMessage());
        }
    }
    
    private void testEncodeDecodeAsInX962() throws AssertionFailedException {
        try {
            System.out.println("testEncodeDecodeAsInX962");

            BigInteger[] rsExpected = {new BigInteger("1234"), new BigInteger("5678")};

            byte[] signature = ECDSASigner.encodeAsInX962(rsExpected[0], rsExpected[1]);

            BigInteger[] rsActual = ECDSASigner.decodeAsInX962(signature);

            assertEquals("r", rsExpected[0], rsActual[0]);
            assertEquals("s", rsExpected[1], rsActual[1]);
        } catch (IOException ex) {
            ex.printStackTrace();
            fail("IOException: " + ex.getMessage());
        }
    }
}
